package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

func main() {
	url := "http://www.baidu.com"
	start := time.Now()
	value, err := New2(httpGet2).get(url)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("url : [%s] take time : [%s] size : [%d] \n", url, time.Since(start), len(value.([]byte)))
}

type Func2 func(url string) (interface{}, error)

type result2 struct {
	value interface{}
	err   error
}

type entry2 struct {
	res   result2
	ready chan struct{}
}

type request struct {
	url      string
	response chan<- result2
}

type Memo2 struct {
	request chan request
}

func (m *Memo2) ask(f Func2) {
	cache := make(map[string]*entry2)
	for req := range m.request {
		e := cache[req.url]
		if e == nil {
			e = &entry2{ready: make(chan struct{})}
			cache[req.url] = e
			go e.call(f, req.url)
		}
		go e.deliver(req.response)
	}

}

func (e *entry2) call(f Func2, key string) {
	e.res.value, e.res.err = f(key)
	if e.res.err != nil {
		log.Fatal(e.res.err)
	}
	close(e.ready)
}

func (e *entry2) deliver(response chan<- result2) {
	<-e.ready
	response <- e.res
}

func (m *Memo2) get(url string) (interface{}, error) {
	response := make(chan result2)
	m.request <- request{url, response}
	res := <-response
	return res.value, res.err
}

func New2(f Func2) *Memo2 {
	m := &Memo2{request: make(chan request)}
	go m.ask(f)
	return m
}

func httpGet2(uri string) (interface{}, error) {
	rsp, err := http.Get(uri)
	if err != nil {
		return nil, err
	}
	defer rsp.Body.Close()
	return ioutil.ReadAll(rsp.Body)
}
